package at.tugraz.hidipic.web;

import at.tugraz.hidipic.model.*;
import at.tugraz.hidipic.service.DataAccessService;
import at.tugraz.hidipic.web.bean.CreateResultBean;
import at.tugraz.hidipic.web.execution.DefaultMethodImpl;
import at.tugraz.hidipic.web.propertyeditor.ProjectPropertyEditor;
import at.tugraz.hidipic.web.propertyeditor.UserPropertyEditor;
import at.tugraz.hidipic.web.util.annotation.FormElement;
import java.lang.reflect.Field;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.propertyeditors.CustomCollectionEditor;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class CreateController {

    @Value("${upload.path}")
    private String path;
    private final Logger LOG = Logger.getLogger(CreateController.class);
    @Autowired
    private HibernateTemplate hibernateTemplate;
    @Autowired
    DataAccessService dataAccessService;

    @InitBinder()
    protected void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Set.class, "authorities", new CustomCollectionEditor(Set.class) {

            @Override
            protected Object convertElement(Object element) {
                if (element instanceof String) {
                    CustomGrantedAuthority cga = new CustomGrantedAuthority();
                    String elem = (String) element;
                    cga.setAuthority(elem);
                    return cga;
                }
                return element;
            }
        });
        binder.registerCustomEditor(Set.class, "methods", new CustomCollectionEditor(Set.class) {

            @Override
            protected Object convertElement(Object element) {
                if (element instanceof String) {
                    Method method = new Method();
                    String elem = (String) element;
                    method.setName(elem);
                    return method;
                }
                return element;
            }
        });
        binder.registerCustomEditor(Set.class, "researchers", new CustomCollectionEditor(Set.class) {

            @Override
            protected Object convertElement(Object element) {
                if (element instanceof String) {
                    User user = new User();
                    String elem = (String) element;
                    user.setUsername(elem);
                    return user;
                }
                return element;
            }
        });
        binder.registerCustomEditor(Set.class, "inputData", new CustomCollectionEditor(Set.class) {

            @Override
            protected Object convertElement(Object element) {
                if (element instanceof String) {
                    String elem = (String) element;
                    InputDataItem data = hibernateTemplate.get(InputDataItem.class, elem);
                    return data;
                }
                return element;
            }
        });
        binder.registerCustomEditor(User.class, "projectLeader", new UserPropertyEditor());
        binder.registerCustomEditor(Project.class, "parent", new ProjectPropertyEditor());
    }

    /**
     * request for adding a result, executing a method
     *
     * @param bean
     * @param request
     * @return a redirect to home
     */
    @RequestMapping(value = "/researcher/result/add", method = RequestMethod.POST)
    public String doStartMethod(@ModelAttribute("bean") CreateResultBean bean, HttpServletRequest request) {
        try {
            User loggedIn = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            User user = hibernateTemplate.get(User.class, loggedIn.getUsername());
            SubProject sp = hibernateTemplate.get(SubProject.class, bean.getSubProject());
            if (!dataAccessService.isSubProjectAvailableToUser(sp, user)) {
                return "redirect:/accessdenied";
            }
            Set<InputDataItem> inputData = bean.getInputData();
            Map<String, Object> outgoingData = new HashMap<String, Object>();
            Map<String, String> urlList = new HashMap<String, String>();
            Set<InputDataItem> actualInputs = new HashSet<InputDataItem>();
            if (inputData != null) {
                for (InputDataItem d : inputData) {
                    String dataId = request.getParameter(d.getName());
                    if (dataId != null && dataId.length() > 0) {
                        urlList.put(dataId, d.getName());
                        actualInputs.add(d);
                    }
                }
            }
            outgoingData.put("inputData", urlList);
            Method method = hibernateTemplate.get(Method.class, bean.getMethod());
            Class configcls = Class.forName("at.tugraz.hidipic.model." + method.getName());
            ConfigurationObject config = (ConfigurationObject) configcls.newInstance();
            Map<String, String> parameters = new HashMap<String, String>();
            if (config != null) {
                Class beanClass = config.getClass();
                for (Field f : getAnnotatedFields(beanClass, FormElement.class)) {
                    FormElement fe = f.getAnnotation(FormElement.class);
                    if (fe != null) {
                        f.setAccessible(true);
                        String param = request.getParameter(f.getName());
                        parameters.put(fe.identifier(), param);
                        for (java.lang.reflect.Method m : getAnnotatedSetters(beanClass)) {
                            FormElement me = m.getAnnotation(FormElement.class);
                            if (me != null) {
                                if (fe.identifier().equals(me.identifier())) {
                                    m.invoke(config, param);
                                }
                            }
                        }
                    }
                }
            }
            hibernateTemplate.save(config);
            outgoingData.put("parameters", parameters);
            Class execClf = Class.forName("at.tugraz.hidipic.web.execution.impl." + method.getName() + "Impl");
            DefaultMethodImpl exec = (DefaultMethodImpl) execClf.newInstance();
            exec.setHibernateTemplate(hibernateTemplate);
            exec.setSubProject(sp);
            exec.setInputData(actualInputs);
            exec.setMethod(method);
            exec.setConfig(config);
            exec.setCreator(user);
            exec.setPath(path);
            exec.setStartDate(new Date());
            exec.setMethodName(method.getShowName());
            exec.run(outgoingData);
        } catch (Exception e) {
            LOG.error(e);
        }
        return "redirect:/home";
    }

    /**
     * request for showing the form for creating a result with the chosen method
     *
     * @param projectId
     * @param subProjectId
     * @param methodId
     * @return the model and view for the form
     */
    @RequestMapping(value = "/create/project/{projectId}/subproject/{subProjectId}/method/{methodId}*", method = RequestMethod.GET)
    public ModelAndView handleRequest(@PathVariable("projectId") String projectId, @PathVariable("subProjectId") String subProjectId, @PathVariable("methodId") String methodId) {
        try {
            ModelAndView mav = new ModelAndView("create");
            User loggedIn = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            User user = hibernateTemplate.get(User.class, loggedIn.getUsername());
            List<DataItem> inputData = dataAccessService.getCreatedDataForUser(user);
            Method method = hibernateTemplate.get(Method.class, methodId);
            int pid = -1;
            int spid = -1;
            try {
                pid = Integer.parseInt(projectId);
                spid = Integer.parseInt(subProjectId);
            } catch (Exception e) {
                LOG.error(e);
            }
            Project project = hibernateTemplate.get(Project.class, pid);
            SubProject subProject = hibernateTemplate.get(SubProject.class, spid);
            if (!dataAccessService.isSubProjectAvailableToUser(subProject, user)) {
                return new ModelAndView("accessdenied");
            }
            Class configcls = Class.forName("at.tugraz.hidipic.model." + method.getName());
            ConfigurationObject config = (ConfigurationObject) configcls.newInstance();
            CreateResultBean bean = new CreateResultBean();
            bean.setProject(project.getId());
            bean.setSubProject(subProject.getId());
            bean.setMethod(method.getName());
            mav.addObject("inputs", inputData);
            mav.addObject("bean", bean);
            mav.addObject("method", method);
            mav.addObject("project", project);
            mav.addObject("subproject", subProject);
            mav.addObject("config", config);
            return mav;
        } catch (Exception ex) {
            LOG.error(ex);
        }
        return new ModelAndView("errorpage");
    }

    /**
     * get annotated fields for a class
     *
     * @param clazz
     * @param annotation
     * @return the fields
     */
    private List<Field> getAnnotatedFields(Class clazz, Class annotation) {
        ArrayList<Field> fields = new ArrayList<Field>();
        for (Field f : clazz.getDeclaredFields()) {
            if (f.getAnnotation(annotation) != null) {
                fields.add(f);
            }
        }
        return fields;
    }

    /**
     * get all annotated setters of a class
     *
     * @param clazz
     * @return the method-list
     */
    private List<java.lang.reflect.Method> getAnnotatedSetters(Class clazz) {
        ArrayList<java.lang.reflect.Method> methods = new ArrayList<java.lang.reflect.Method>();
        for (java.lang.reflect.Method meth : clazz.getMethods()) {
            if (meth.getName().startsWith("set") && meth.getAnnotation(FormElement.class) != null) {
                methods.add(meth);
            }
        }
        return methods;
    }
}
